iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
IT Operation

寫完微服務然後呢?走向平台工程的黃金路徑系列 第 5

Day 05 - CRD 是什麼:讓 Kubernetes 認識自訂資源

  • 分享至 

  • xImage
  •  

昨天將 todo-apiweb 的共用設定和環境差異拆開了,但兩個服務仍要各自準備 DeploymentService,並知道哪些 label、probe 與 selector 不能寫錯。文件可以說明規則,卻不能阻止一份不合規的 YAML 進入叢集。

先回頭看既有的 todo-apiweb。它們真正要表達的可能只有:使用哪個 image、在哪個 port 提供服務。其餘 Kubernetes 細節不該每次從頭決定。Custom Resource Definition(CRD) 讓我們能把這種團隊語言加入 Kubernetes API,先定義一份可驗證的服務合約。

問題與設計:讓 Kubernetes 認得團隊的語言

Kubernetes 原生知道 DeploymentServiceConfigMap 等資源,卻不知道「可交付的微服務」代表什麼。安裝 CRD 後,API Server 會接受新的資源型別,例如 Microservice;它也會依 CRD 定義的 schema 檢查送進來的資料。

這裡有四個名稱很像、責任卻不同的角色:

  • CRD:資源型別的 API 定義,包含 group、version、名稱與 schema。
  • Custom Resource(CR):開發者建立的一筆實際資源,例如 todo-api
  • Controller/Operator:監看 CR,將意圖轉成 Deployment、Service 等原生資源,並回報 status
  • API Server:依 CRD schema 驗證並儲存 CR,但不會自動創造任何工作負載。

最後一點不能混在一起看。CRD 定義 Kubernetes「認得什麼資料」;Controller/Operator 負責「收到資料後要做什麼」。因此,安裝 CRD 後能執行 kubectl get microservices,不表示 todo-apiweb 已經由 CR 部署。沒有 controller 的 CR,只是 API Server 保存的一筆資料。

先設計最小合約,不要複製 PodSpec

平台自定義合約資源,不要把整個 Kubernetes PodSpec 原封不動塞進 CRD。這樣看似保留彈性,實際上只是替原生 YAML 換了一層名字,平台無法提供可靠預設,開發者也還是要學會所有低階選項。

Todo 系統的第一版只讓服務擁有者決定 image 與服務 port。副本策略、受管 label、健康檢查、預設 resource policy 與 telemetry 注入留給平台處理。這份 CRD 的核心 schema 如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: microservices.platform.example.io
spec:
  group: platform.example.io
  scope: Namespaced
  names:
    plural: microservices
    singular: microservice
    kind: Microservice
    shortNames:
      - ms
  versions:
    - name: v1alpha1
      served: true
      storage: true
      subresources:
        status: {}
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required:
                - image
                - port
              properties:
                image:
                  type: string
                  minLength: 1
                port:
                  type: integer
                  minimum: 1
                  maximum: 65535
            status:
              type: object
              properties:
                phase:
                  type: string
                observedGeneration:
                  type: integer
                managedResources:
                  type: object
                  properties:
                    deployment:
                      type: string
                    service:
                      type: string

v1alpha1 表示合約仍可能演進,但不表示可以隨意破壞已存在的 CR。即使在 alpha 階段,也要考慮新增欄位是否相容、舊資料如何讀取,以及未來 version conversion 的成本。

先把 todo-apiweb 表達成服務意圖

安裝 CRD 後,服務團隊提交的 todo-api 不再是一大段 Deployment

apiVersion: platform.example.io/v1alpha1
kind: Microservice
metadata:
  name: todo-api
  namespace: todo
  labels:
    app.kubernetes.io/owner: todo-team
spec:
  image: ghcr.io/yrw9281/it30-todo-api:0.1.0
  port: 8080

web 使用相同合約,只差在 image 與 port:

apiVersion: platform.example.io/v1alpha1
kind: Microservice
metadata:
  name: web
  namespace: todo
  labels:
    app.kubernetes.io/owner: todo-team
spec:
  image: ghcr.io/yrw9281/it30-todo-web:0.1.0
  port: 80

spec 是服務擁有者提交的意圖:要使用哪個 image、在哪個 port 提供服務。以下狀態則由平台觀察後寫回,不應由服務擁有者手動宣稱:

# Operator 觀察後寫回的狀態範例
status:
  observedGeneration: 4
  phase: Progressing
  managedResources:
    deployment: todo-api
    service: todo-api

啟用 status subresource 後,平台 controller 可以取得專門更新 status 的權限,而一般服務提交者只管理 spec。這個界線避免把「資源已提交」誤當成「服務已健康」。

在 status 設計上,早期常直接使用 phase 列舉狀態(如 Progressing、Ready),但現代 Kubernetes API 更推薦搭配 observedGeneration 與標準的 conditions 陣列。這樣既能表達詳細的健康狀態,也能讓使用者分辨 controller 是否已處理到最新的 spec。

CRD 不是萬能抽象

將概念提升成 CRD 有成本。每個欄位都是未來要維護的 API 承諾。太早抽象,可能把尚未穩定的部署細節鎖死;太晚抽象,各團隊又會各自發明 YAML。適合做成 CRD 的能力,通常會跨多個服務重複出現、需要 controller 持續維持,而且有清楚的領域語意。

CRD 也不會取代 RBAC、Policy 或 Git review。它只能約束結構與欄位範圍;誰可以建立資源、image 是否來自受信任的 GHCR repository、何時能進 Production,仍需要另外定義治理規則。

測試與驗證

先將 CRD 套用到叢集,再用 server-side dry run 驗證 API Server 是否接受 CR:

# 安裝資源型別;這一步會真的寫入叢集
kubectl apply -f src/1-kubernetes/crd/microservice-crd.yaml

# 兩個合法 CR 都應通過 schema 驗證,但不會產生 Deployment
kubectl apply --server-side --dry-run=server \
  -f src/1-kubernetes/crd/todo-api.yaml \
  -f src/1-kubernetes/crd/web.yaml

# port 為 65536,應在寫入前被 schema 拒絕
kubectl apply --server-side --dry-run=server \
  -f src/1-kubernetes/crd/invalid-todo-api.yaml

todo-apiweb 的 CR 可以通過驗證,卻不會自動產生 Deployment,因為 Operator 尚未實作。invalid-todo-api.yamlport: 65536 超出 schema 的上限,API Server 應拒絕寫入。若輸入未定義的欄位,API Server 預設會因結構驗證不符而拒絕寫入;即使在允許剪裁(Pruning)的設定下被忽略,也不能把它誤解為驗證成功。

這組結果確認 CRD 是合約,不是自動化本身。要讓 Microservice CR 真的展開成 Kubernetes 原生資源,還需要由 controller 持續觀察 CR 並執行 reconcile

結語

CRD 讓 Kubernetes 開始理解平台自定義的領域語言,但它只定義服務意圖的形狀。每個 Microservice 變更仍需要可信的保存位置與審查機制,才能知道環境應維持哪一份設定,並在需要時追溯與重建,而我們明天會試著把 Git 當作單一事實來源(SSoT)。


上一篇
Day 04 - Helm 與 Kustomize:怎麼管理不同環境的設定
下一篇
Day 06 - GitOps 怎麼用 Git 管理部署狀態
系列文
寫完微服務然後呢?走向平台工程的黃金路徑7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言